package info.empathymobile.forecast.testenv;

import info.empathymobile.forecast.testenv.beans.TestBaseData;
import info.empathymobile.forecast.testenv.beans.TestLog;
import info.empathymobile.forecast.testenv.business.LocalDBConnection;
import info.empathymobile.forecast.util.ComponentsInfo;
import info.empathymobile.forecast.util.DeviceInfo;
import info.empathymobile.forecastmethods.ForecastMethod;
import info.empathymobile.forecastmethods.ForecastMethodFactory;
import info.empathymobile.forecastmethods.HoltWintersExpSmooth;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import android.content.Context;
import android.os.AsyncTask;

public class ForecastTestController {
	private Context myContext;
	private List<TestBaseData> testBaseDataParams;
	private String selectedAlgorithm;
	private int forecastHorizon;
	private LocalDBConnection localDBConnection;
	
	public ForecastTestController(Context context){
		this.myContext = context;
		this.localDBConnection = new LocalDBConnection(context);
	}
	
	public void startForecastTest(List<TestBaseData> testBaseDataParams, String selectedAlgorithm, int forecastHorizon) {
		this.testBaseDataParams = testBaseDataParams;
		this.selectedAlgorithm = selectedAlgorithm;
		this.forecastHorizon = forecastHorizon;
		new ForecastTestsThread().execute();
	}
	
	private class ForecastTestsThread extends AsyncTask<Void, Void, Boolean> {
		@Override
		protected Boolean doInBackground(Void... params) {
//LEITURA DOS DADOS			
			printLogLine("Forecast Tests Initiated at: " + new Date(System.currentTimeMillis()).toString());
			printLogLine("Forecast Tests Preparation Started!");
			
			printLogLine("Getting Test Parameters [Algorithms Selection, Forecast Horizon and Test Base Data Selection]...");
	
			printLogLine("Loading Tests Data...with " + testBaseDataParams.size() + " params choices: ");
			
			printLogLine("Getting Test Data From Local DB...");
			List<TestBaseData> testBaseDataCollection = new ArrayList<TestBaseData>();
			for (TestBaseData param : testBaseDataParams) {
				testBaseDataCollection.add(localDBConnection.getTestBaseDataByParams(param));
			}
			
			if(!testBaseDataCollection.isEmpty()){
				printLogLine("Test Base Data Collected!");
				
				printTestBaseDataCollected(testBaseDataCollection);
				
	//EXECUÇÃO DOS TESTES
				printLogLine("Forecast Tests Execution Started!");
				
				printForecastTestSelectedConfigs(selectedAlgorithm, testBaseDataCollection.get(0).getBaseDataType(), forecastHorizon);
				
				List<TestLog> results = executeTests(selectedAlgorithm, testBaseDataCollection, forecastHorizon);
				printLogLine(results.size() + "/" + testBaseDataCollection.size() + " successfully executed");
				
				printLogLine("All Forecast Tests Has Finished!");
				
	//GRAVAÇÃO DOS RESULTADOS
				printLogLine("Saving Tests results on local DB...");
				new LocalDBConnection(myContext).saveTestLogCollection(results);
				printLogLine("DONE!");
			}
			else {
				printLogLine("# Forecast Tests Data Load Failed (Pull From Server) #");
			}
			printLogLine("-------------------------------------------\n\n\n");
			return true;
		}
	}
	
	public List<TestLog> executeTests(String algorithm, List<TestBaseData> testBaseDataCollection, int forecastHorizon) {
		List<TestLog> testResults = new ArrayList<TestLog>();
		ForecastMethodFactory forecastMethodFactory = new ForecastMethodFactory();
		int withoutMinBaseDataLenght = 0;
		
		for (TestBaseData testBaseData : testBaseDataCollection) {
			if(testBaseData.getBaseData().length >= TestBaseData.MIN_BASE_DATA_LENGHT){
				ForecastMethod selectedForecastMethod = forecastMethodFactory.getForecastMethod(algorithm);
				
				int[] timeSeries = null;
				if(algorithm.equals(HoltWintersExpSmooth.NAME)){
					timeSeries = baseDataDeleteHorizon(forecastHorizon, testBaseData.getBaseData());
				}
				else {
					timeSeries = baseDataBlankHorizon(forecastHorizon, testBaseData.getBaseData());
				}
				
				System.gc(); //the system gc its for precision purpose
				try{Thread.sleep(10000);}catch(InterruptedException e){e.printStackTrace();}
				
				long execStartAt = System.nanoTime();
				long initialMemory = ComponentsInfo.getUsedMemory();
				int[] timeSeriesWithForecast = selectedForecastMethod.doForecasting(timeSeries, forecastHorizon);
				long usedMemory = ComponentsInfo.getUsedMemory() - initialMemory;
				double duration = ((double)(System.nanoTime() - execStartAt))/1000000.00;
				
//				double cpuPowerUsageNowInMilliAmpers = ComponentsInfo.getCpuPowerUsageNow();
//				double cpuPowerUsageNowInWatts = (cpuPowerUsageNowInMilliAmpers/1000.00) * 4.34;
//				double cpuTotalPowerUsageInJ = cpuPowerUsageNowInWatts * (duration/1000.00); 
				
				TestLog testLog = new TestLog();
				//Execution Config Info
				testLog.setAlgorithmName(selectedForecastMethod.getName());
				testLog.setAlgorithmVersion(selectedForecastMethod.getVersion());
				testLog.setForecastHorizon(forecastHorizon);
				testLog.setTestBaseData(testBaseData);
				//Execution Env Info
				testLog.setDeviceModel(DeviceInfo.getDeviceName());
				testLog.setSOVersion(DeviceInfo.getDeviceSOVersionNumber());
				//Test Results
				testLog.setForecastedData(extractForecastedData(forecastHorizon, timeSeriesWithForecast));
				testLog.setDuration((long) duration);
				testLog.setMemoryUsed(usedMemory);
//				testLog.setEnergyUsed((float) cpuTotalPowerUsageInJ);
				
				testResults.add(testLog);
			}
			else {
				withoutMinBaseDataLenght++;
			}
		}
		printLogLine("Forecast Tests using " + algorithm + " Finalized");
		
		printLogLine(withoutMinBaseDataLenght + " tests baseData without minimal size");
		
		return testResults;
	}

	private int[] extractForecastedData(int horizon, int[] timeSeriesWithForecast) {
		int[] forecastedData = new int[horizon];
		int t = timeSeriesWithForecast.length-1;
		for (int i = 0; i < horizon; i++) {
			forecastedData[i] = timeSeriesWithForecast[t-(horizon-1)+i];
			if(forecastedData[i] < 0){
				forecastedData[i] = 0;
			}
		}
		
		return forecastedData;
	}

	private int[] baseDataDeleteHorizon(int horizon, int[] baseData) {
		int lenghtMinusHorizon = baseData.length-horizon;
		int[] baseDataMinusHorizon = new int[lenghtMinusHorizon];
		for (int i = 0; i < lenghtMinusHorizon; i++) {
			baseDataMinusHorizon[i] = baseData[i]; 
		}
		
		return baseDataMinusHorizon;
	}
	
	private int[] baseDataBlankHorizon(int horizon, int[] baseData) {
		int lenghtMinusHorizon = baseData.length-horizon;
		int[] baseDataMinusHorizon = new int[baseData.length];
		for (int i = 0; i < lenghtMinusHorizon; i++) {
			baseDataMinusHorizon[i] = baseData[i];
		}
		
		return baseDataMinusHorizon;
	}

	private void printLogLine(String logMsg){
		((TestLogOutput) myContext).printLogMsg(logMsg);
	}
	
	public void printForecastTestSelectedConfigs(String selectedAlgorithm, String dataType, int forecastHorizon) {
		printLogLine("Forecast Algorithm: " + selectedAlgorithm);
		printLogLine("Forecast Data Type: " + dataType);
		printLogLine("Forecast Horizon: " + forecastHorizon);
		
	}

	private void printTestBaseDataCollected(List<TestBaseData> testBaseDataCollection) {
		printLogLine("Test Base Data List:");
		printLogLine("User UUID | Data Type | Threshold | Days Ago | Created At | Array Size");
		for (TestBaseData testBaseData : testBaseDataCollection) {
			printLogLine(testBaseData.toString());
		}
		printLogLine("Total Number: " + testBaseDataCollection.size());
	}
}
